package flp.solvers.annealing;

import flp.solvers.*;
import flp.*;
import flp.problem.*;
import flp.exceptions.*;
import flp.Layout;
import flp.exceptions.LayoutBuildException;
import flp.problem.ProblemInstance;
import java.util.Random;

/**
 * Klasa szukajaca rozwiazania FLP przy uzyciu algorytmu symulowanego wyzarzania.
 * 
 * @author kuzu i rudy
 */

public class AnnealingSolver extends Solver{

    static {
        preferableCostFunction = Layout.CostFunction.DAGSP;
    }
    
    /** Ustawienia z jakimi bedzie pracowac obecny algorytm. */
    AnnealingSettings solverSettings;
    
    public AnnealingSolver(ProblemInstance problem, AnnealingSettings settings, IOptimizationMonitor optMon) {
        super(problem, optMon);
        this.solverSettings=settings;
    }
    
    /**
    * Metoda wybierajaca sasiada danego Layoutu.
    * 
    * @return Sasiad Layoutu podanego jako argument.
    * 
    * @param current Layout, którego sasiada chcemy wylosowac.
    * @param T Temperatura dla jakiej wlasnie pracujemy.
    */
    
    public Layout findNext(Layout current, double T) throws LayoutBuildException, FlowImpossibleException
    {
        Random r=new Random();
        int toChange=r.nextInt(current.getSize());                      //ktorego building site'a bedziemy zabudowywac
        Layout next=current.clone();
        double probab[]=new double[toSolve.getRiverFacilitiesCount()+5]; //tablica prawdopodobienstw(licznikow) wyborow roznych Layoutow        
        
        next.setPlace(toChange, null);
        if(next.checkCorrectness()) {
            probab[0]=Math.exp((current.getCost(preferableCostFunction)-next.getCost(preferableCostFunction))/T);
            //if(Double.isInfinite(probab[0]) || probab[0]==0){System.out.println(current);System.out.println(next);
            //    throw new LayoutBuildException("Temperatura koncowa jest zbyt niska1 "+(current.getCost(preferableCostFunction)-next.getCost(preferableCostFunction)));
        
            //}
        }
        else probab[0]=0;
        
        next.setPlace(toChange, new CleaningUsage());
        if(next.checkCorrectness()) {
            probab[1]=Math.exp((current.getCost(preferableCostFunction)-next.getCost(preferableCostFunction))/T);
             //if(Double.isInfinite(probab[1]) || probab[1]==0)
             //    throw new LayoutBuildException("Temperatura koncowa jest zbyt niska2");
        }    
        else probab[1]=0;
        
        for(int i=0;i<toSolve.getRiverFacilitiesCount();i++) {
            next.setPlace(toChange, new InboundUsage(i));
            if(next.checkCorrectness()) {
                probab[2*i+2]=Math.exp((current.getCost(preferableCostFunction)-next.getCost(preferableCostFunction))/T);
                //if(Double.isInfinite(probab[2*i+2]) || probab[2*i+2]==0)
                //    throw new LayoutBuildException("Temperatura koncowa jest zbyt niska3");
            }
            else probab[2*i+2]=0;
            
            next.setPlace(toChange, new OutboundUsage(i));
            if(next.checkCorrectness()) {
                probab[2*i+3]=Math.exp((current.getCost(preferableCostFunction)-next.getCost(preferableCostFunction))/T);
                //if(Double.isInfinite(probab[2*i+3]) || probab[2*i+3]==0)
                //    throw new LayoutBuildException("Temperatura koncowa jest zbyt niska4");
            }
            else probab[2*i+3]=0;
        }
         
        double sum=0.0;
        for(int i=0;i<2*toSolve.getRiverFacilitiesCount()+2;i++) {
            sum+=probab[i];
        }

        if(Double.isInfinite(sum) || sum==0)
            throw new LayoutBuildException("Temperatura koncowa jest zbyt niska");

        probab[0]/=sum;
        for(int i=1;i<2*toSolve.getRiverFacilitiesCount()+1;i++) {
            probab[i]/=sum;
            probab[i]+=probab[i-1];
        }
        probab[2*toSolve.getRiverFacilitiesCount()+1]=1;    // żeby się nie popsuło przez zaokrąglenia
        double p=r.nextDouble();
        int iter=0;
        while(probab[iter]<p) iter++;
    
        if(iter==0) {next.setPlace(toChange, null);}
        else if(iter==1) next.setPlace(toChange, new CleaningUsage());
        else if(iter%2==0) next.setPlace(toChange, new InboundUsage((iter-2)/2));
        else next.setPlace(toChange, new OutboundUsage((iter-3)/2)); 
  
        return next;
    }
    
    
    /** 
     * Metoda przeprowadzająca obliczenia.
     * 
     * @return Najlepsze znalezione rozwiązanie.
    */
    @Override
    public Layout computeSolution() throws LayoutBuildException, FlowImpossibleException
    {
        
      try {
            
       
        Random r=new Random();
        // najlepsze znalezione rozwiazanie w historii, na poczatek losowe
        Layout globalBest=Layout.getInstance(toSolve);

        for(int i=0;i<solverSettings.getTries();i++){
            // najlepsze rozwiazanie w tej probie(znaczy takie, z ktorym wiazemy jakies nadzieje)
            Layout localBest=Layout.getInstance(toSolve);

            double T=0;    //temperatura
            // wybieramy temperaturę początkową, maksymalną energie z kilku prób z pewną nadwyżką
            for(int j=0;j<10;j++) {

                double tmp=(double)(Layout.getInstance(toSolve).getCost(preferableCostFunction));
                if(tmp>T) T=tmp;
            }
            T*=1.3;
            double T0=T;
            int q=0;
            while(T>solverSettings.getEpsilon()) {
                q++;
                for(int j=0;j<solverSettings.getIterations();j++) {
                        
                        if (stoppedFlag){
                            return globalBest;
                        }
                    
                        Layout nextLayout=findNext(localBest,T);              
                        //int dE=nextLayout.getCost(preferableCostFunction)-localBest.getCost(preferableCostFunction);
                        if(nextLayout.checkCorrectness() && nextLayout.getCost(preferableCostFunction)<globalBest.getCost(preferableCostFunction)) globalBest=nextLayout.clone();
                        //if(nextLayout.checkCorrectness() && (dE<0 || r.nextDouble()<Math.exp(-dE/T))) localBest=nextLayout.clone();
                        if(nextLayout.checkCorrectness()) localBest=nextLayout.clone();
                        
                        /** @autor Mateusz
                         *  Fragment pobierający dane do monitora
                         *  values = { iteracja, próba, najlepszy koszt lokalnie, najlepszy koszt globalnie, temperatura }
                         */
                        
                        Object[] values = {j, i , localBest.getCost(preferableCostFunction), globalBest.getCost(preferableCostFunction), T};
                        optMonitor.markProgress(values);
                }

                if(solverSettings.getCooling()==0) T=0.9*T;
                else T=T0/(1+Math.log(q*q)*Math.log(q*q)*Math.log(q*q)*Math.log(q*q));
            }    
        }
        if(!globalBest.checkCorrectness())
            throw new LayoutBuildException("Znaleziony layout nie spelnia ograniczen, przepraszam");
        globalBest.getCost(preferableCostFunction);
        return globalBest;
    
      } catch (LayoutBuildException lbe){
        // metoda zwraca null, ktory handler interpretuje jako rzucenie
        // wyjatku "LayoutBuildException"  
        return null;
      }
    }
    
}
